IoT CONFERENCE Blog

Building a Docker Swarm with the Raspberry Pi

May 5, 2020

In this article we are going to learn how to install and run Docker on the Raspberry Pi. We are going to use a simple Docker Image as our example that will deliver a JSP page with Tomcat. By the end of the article, we are going to build up a swarm with three Raspberry Pi.

With Docker you can install applications with all their dependencies in images. The images are then easily exchangeable between systems. There are already a lot of preconfigured images that you can use freely and adapt to your own needs. Using them saves you a lot of time and resources. When an image is started, a container is created. Images and containers behave similarly to each other, like Java classes and their instances. In theory, any number of containers can be created from an image. The only limitation is the hardware on which the containers run. However, this limit can easily be moved up if you use a Docker Swarm. With a swarm you can connect several Docker installations running on different computers. They can then be managed like one installation. A Docker Swarm offers a certain tolerance against errors;

The images are not changed during a container’s runtime. The containers hold their own data in an overlay file system. Each newly started container initially has the same data as the image from which it was created. However, there is also the possibility to restart a container with its data. We will go into this in more detail later. Additionally, Docker provides so-called volumes. With these it is possible to use parts of the host file system in a container and thus to store data persistently, independent of the status of the container. There are two Docker variants:

  • Docker EE (Enterprise Edition) is intended for professional use and comes with costs attached.
  • Docker CE (Community Edition) can be downloaded and used for free.

We are going to use Docker CE in this article.

Set up

For our swarm we use three Raspberry Pi of type 3, on which we install the current Raspbian Buster Lite. We use the Lite variant, because it does not contain any unnecessary programs. You can download Raspbian Buster Lite for free from the download page of the Raspberry Project [1]. Once you have saved the ZIP file on your disk, all you have to do is to unzip it and write it to the SD cards with a tool of your choice. We need one card for each Raspberry. It will take a while for all of the cards to be written. IT people who are familiar with Murphy’s law (“Everything that can go wrong will go wrong.”) Will prepare one more card right away, to be on the safe side.

You should label each Raspberry uniquely so that you don’t get confused later, and it helps to give the Raspberries static IP addresses and unique hostnames. Table 1 shows an example of how the assignment can be carried out. Of course, you have to choose IPs that work in your own network. Figure 1 shows the labeled Raspberry swarm. You should use a fan to provide some airflow, because the Raspberries can get quite warm when they are so closely together.

Fig. 1: The Raspberry Swarm

 

Hostname  IP Label
docker01 192.168.3.70 01
docker02 192.168.3.71 02
docker03 192.168.3.72 03

Table 1: Configuration example of the swarm

When you have everything ready, it is time to put the SD cards into the Raspberries. We describe here what to do for the setup, using the computer docker01 as an example. For the other two computers the same steps have to be carried out analogously.

At the initial boot, the Raspberry adjusts the size of the root file system to the used SD card, then it reboots automatically. After the reboot we can log in onto the Raspberry as usual (User: pi Password: raspberry ). First of all, you should activate the SSH server on the Raspberry, so that you can access the Raspberry from your PC. You can do this by using the command line tool raspi-config . The tool must be run with superuser rights ( sudo raspi-config ). Now you can activate the SSH server in the menu item Interfacing Options | P2 SSH.

From now on you can continue working from your PC. Linux users can simply use the command ssh pi @ <RASPI_IP> for this. If you are using a different operating system, you will need to download a tool, Putty for instance, in order to establish the SSH connection.

Now we give each Raspberry a static IP address, which makes it easier to distinguish them from one-another. To accomplish this, we edit the file, in which the network settings are stored ( sudo nano /etc/dhcpcd.conf ). In Listing 1 you can see the location in the file that needs to be changed in order to use a static IP.

Listing 1: Using a static IP

# Example static IP configuration:

interface eth0

static ip_address = 192.168.3.70 / 24

static routers = 192.168.3.1

static domain_name_servers = 192.168.3.1

The hostnames of the Raspberries are currently all the same. Therefore, we should change them as well. The file / etc / hostname contains the hostname of a Unix machine. Please open the file with root privileges and change the name ( sudo nano / etc / hostname ). Now there is a second file which should be adapted: / etc / hosts . Here, you should also replace the name raspberry with the corresponding name: docker0x . The file / etc / hosts will be discussed again in the next section. In order for our changes to take effect, you should reboot the raspberry ( sudo reboot ).

Now we can access the three Raspberries using their IP addresses. It would be nice, if we could use the host names instead of the IP. An easy way to do so, is to enter the host names with the IP addresses into the hosts file. With the hosts file it is possible to use a local name resolution. On Linux systems the file can be found under / etc / . On Windows it is located in c: \ windows \ system32 \ drivers \ etc \ . You will need a superuser permission to edit it. Now you can simply add the hostnames and IPs to the file, as shown in Listing 2. Once the file is saved, you can use the names to access the Raspberries. However, the names only work on the computer to which the hosts file is adapted. Now you can also adapt thehosts files on the Raspberries, so that they can address each other by name.

Listing 2: The host names of the individual raspberries

# Raspberry Docker Server

192.168.3.70 docker01

192.168.3.71 docker02

192.168.3.72 docker03

Now we have completed the preparations on all Raspberries and can start the actual Docker installation.

Installing Docker

The installation of Docker on the Raspberries is quite simple. The first step is to bring the Raspberries up to date. Afterwards, you can download and execute an installation script with curl .

 

sudo apt update

sudo apt upgrade

sudo curl -fsSL https://get.docker.com | sh

 

The script will need some time, but it will do all installation steps automatically. You can use the docker version command to check if the installation was successful. It should give you the version of the Docker installation. In order to avoid running Docker as a root user, we now add the user pi to the docker group with the command sudo usermod -aG docker pi . You have to log out and log in again, in order to make the group change effective. Now, all preparations are completed, and we can start working with Docker.

The Docker basics

Before we start to build a swarm with Docker, we should first see what can be done with a single installation. In the first step we will clarify some terms with which we are going to work in a moment:

  • Base Images: The base image is always the starting point for creating your own images. The base images can be downloaded for free from the Docker website [2]. To get an overview of what images are available, you can use the search function of the site [3]. To find packages for the Raspberry you have to check the arm64 box in Architectures. Now, you should select Official Images to see only tested images. There is a manageable number of just over 110 images left. Base images are automatically downloaded when you build your own image for the first time. This may take some time, however. Usually, there is only one service in a base image, which you first have to adapt for your purposes.
  • Dockerfile : The dockerfile controls from which base image your own image should be created. Furthermore, the dockerfile describes which modifications to the image are required. The dockerfile has the file name Dockerfile .
  • Docker Image : A Docker Image is created when a base image has been adjusted using the dockerfile. If we stick to the comparison with Java, the Docker Image is something like a derived class of the base class. It contains all the customizations we need to make our application run the way we want it to.
  • Container : The Docker Container is an instance of an image. It is therefore a copy of the image that is executed. Containers can be created from custom Docker Images or directly from base images. The container has a file system in which it can store data.

To create your own Docker Image, we first need a project directory where we store the Dockerfile: mkdir image_tomcat . Our image should deliver a simple JSP page that displays some data. You can see the source code of the JSP page in Listing 3. The page displays the server name and the timestamp of the server. We do this so that we later know from which node the page was created. The timestamp is built in, so that the page changes with every call. In Listing 4 you can see the dockerfile that creates our image. The first line indicates from which base image our image should be created. The second line defines which time zone it should have. With the ENVclause any environment variables of the image can be changed or set. The following line describes how the image should be modified. In our case a directory is created in the Tomcat webapps folder . We copy the file index.jsp into this directory. In our example the JSP file is located in the same directory as the Dockerfile. In the Dockerfile you can, of course, use much more than the commands that are shown here. An overview of the possibilities is shown in Table 2 .

Listing 3: Docker file

FROM tomcat: 8.0

ENV TZ = “Europe / Berlin”

COPY ./index.jsp /usr/local/tomcat/webapps/myapp/index.jsp

 

 

Listing 4: index.jsp

<! DOCTYPE HTML>

<HTML>

<HEAD>

<TITLE> Hello Raspberry </TITLE>

</HEAD>

<BODY>

Server name: <% try {out.println (InetAddress.getLocalHost ());} catch (Exception e) {}; %> <BR>

Server time: <% = System.currentTimeMillis ()%> <BR> 

</BODY>

</HTML>

 

 

Command Function
ADD Copy file into the image
CMD Execute command at container start (change parameters)
COPY Copy file from current directory into the image
ENTRYPOINT Like CMD, but cannot be overwritten from outside
EPS Set environment variables
EXPOSE Ports of the containers
FROM Which base image should be used?
LABLE Set metadata for the image
RUN Execute command when starting the container (install software)
USER User for execution of CMD, RUN, ENTRYPOINT
VOLUME Volume directory 
WORKDIR Set work directory 

Table 2: Overview of the commands in the dockerfile

To create our Docker Image, we are switching to the directory with the Dockerfile ( cd image_tomcat ) and execute the docker build -t image_tomcat. command. Note that the dot at the end of the command means that the Dockerfile should be taken from the current directory. The parameter -t specifies the name the image should get. With the command docker images we can now see which images exist in our Docker environment. Should you ever have to delete an image, you can do so with the command docker rmi <list of image IDs | names> . An image can only be deleted if it is no longer referenced by any container or other images. Now we start a container from our image with the following command: docker run -p 80: 8080 –name container_tomcat -h host_tomcat image_tomcat.

The terminal is now used for the output of the Tomcat server. Port 8080 of the container is redirected to port 80 of the host system. Now you can open a browser and view the JSP page ( Fig. 2 ). Please now open a second SSH connection to the Docker host. With the command docker ps -a you can now see which containers are or have been running.

Fig. 2: The JSP page in the browser

If you do not specify the parameters for container and host names, they are randomly selected. That has the advantage that you can quickly create many hosts for testing. You can delete a container with the command docker rm <List of Container IDs | Names> .

The documentation on the internal structure of containers is often somewhat sparse. The basic idea is to hide everything that is not necessarily needed for the operation. However, if you want to see what the inside of a container looks like, the following command can help: docker exec -it <container ID | name> / bin / bash . It builds an interactive connection to the container and starts a shell, which is bound to the current terminal. With this shell, we can work inside the container. The shell has root privileges and we can even use it to install software inside the container.

With the command docker stop <Container ID / Name> you can stop a running container. To start it again, use the command docker start <Container ID / Name> . The difference between docker start and docker run is easy to explain. run creates a completely new container, start does start an existing container. An existing container already contains data from its last run. You should keep this small detail in mind.

Now you have learned the basics of Docker and the most important commands. Therefore, I would like to conclude the section about the basics of Docker at this point. Table 3 gives you an overview of all Docker commands and their respective functions.

 

Command Function Command Function
attach Connect container with terminal (Stdin, Stdout) port Show portmappings
build Create / update image from Dockerfile ps Show container
commit Create new image from Container (all changes of the container are then part of the image) pull Get image from registry
cp Copy data (locale FS, container) push Relocate image into registry
create Create new container rename Rename container
diff Check changes restart Restart container
events Evaluate real time events rm Delete container
exec Execute command in container rmi Delete images
export Export container into an archive run Create and start container
History Show history of an image Save Save an image in an archive
images Show list of images search Search on Docker Hub for images
import Import container from archive begin Start container
info Show system information stats Show container state
inspect Show container state and configuration stop Stop container
kill Terminate container Day Create tag
load Load image Top Show running container processes
Log in Login at Docker Registry unpause End break
logout Logout from Docker Registry update Update container configuration
logs Show container log files version Show docker version
Break Stop all container processes wait Wait, unto containers are terminated

Table 3: Oversight of docker commands

Building the swarm 

A Docker swarm consists of at least one manager node and any number of worker nodes. Communication between the nodes is always encrypted. The encryption is managed internally by Docker, so we don’t have to worry about it. Only a few commands are needed to build a Docker swarm. In our case you can use the following command on the docker01 machine: docker swarm init –advertise-addr eth0 . The output contains the command you need to use to add more worker nodes to the swarm. Please execute the command on the two remaining Raspberries.

To check if the swarm is complete, you can use the command docker node ls. In Figure 3 you can see that your swarm now consists of three nodes. The swarm is now fully configured, and we can run containers on it.

Fig. 3: Creation of a swarm

Starting a container in a swarm

We can now run our tomcat_container from the first example in a swarm, without many adjustments. All we need is an additional configuration file ( Listing 5 ) and a slightly different deployment. The configuration file just describes how we turn our images into a service. This file must be placed in the project directory.

 

Listing 5: docker-compose.yml

version: ‘3’

services:

web:

image: image_tomcat

ports: 

– “80: 8080”

 

To start the service in the swarm, please enter the following command in the command line: docker stack deploy -c docker-compose.yml tomcat_swarm . The output of the program looks like an error message at first glance, but it is only a well-intentioned hint that we can ignore in our case. The correct images are distributed automatically when the containers are started. However, this process takes some time.

Currently our service is running on only one node. We can check this with the command docker service ls . In order to extend the service to all nodes, we use the command: docker service scale tomcat_swarm_web = 3 . The images are now transferred to the remaining nodes and started. It takes some time until the image is available on all nodes, then we can see with docker service ls that the service runs on three nodes now.

To check whether the service is actually distributed, we use the following command. It should output a different host name for each call. The hostnames are generated automatically, so they look like hashes: curl -s http: // docker01 / myapp / | grep server name .

Of course, you can also use a browser for testing. Please make sure that you really reload the page, otherwise the browser cache can play a trick on you. If you want to check the state of the complete swarm, you can do so with the command docker stack ps tomcat_swarm .

That’s all it took to turn a single Docker Image into a service in a swarm. You can remove the service from the swarm with the command docker stack rm tomcat_swarm . If you want to make changes to the image now, you have to rebuild it and then repeat the steps for deploying it to the swarm. With the use of the bash history, this is only a matter of a few seconds.

Conclusion

Unfortunately, there are not as many prepared Docker Images for the Raspberry Pi on Docker Hub as there are for the X86. But the most important images are available and maintained, so you can easily use the Raspberry to learn the basics of Docker. Jumping from a single image to a service in a swarm is easy with Docker. Managing the swarm from the command line works really well.

In this article I could only give a small overview of what is possible with Docker, but it’s definitely worthwhile to take a closer look at Docker.

 

Links & Literature

[1] https://www.raspberrypi.org/downloads/raspbian/

[2] https://hub.docker.com

[3] https://hub.docker.com/search